/**
 * <p>Title: AsteroidBelt</p>
 * <p>Description: It ain't black belt, but it hurts</p>
 * @author Guilherme Mauro Germoglio
 */

import java.awt.*;
import javax.swing.*;

public class AsteroidBelt {

  // atributes

  private int numberOfAsteroids;
  private Asteroid[] asteroids;
  private int minimumNumberOfVertex;
  private AsteroidThread[] asteroidsThreads;
  private JPanel panel;
  private int mediumRadius;
  private int initialNumberOfAsteroids;
  private boolean paused;

  // methods

  /**
   * AsteroidBelt's constructor. It needs the number of asteroids and the maximum
   * number of vertex an asteroid can have
   * @param numberOfAsteroids The number of asteroids
   * @param minimumNumberOfVertex The minimum number of vertex an asteroid can have
   * @param mediumRadius The medium radius of all asteroids that will be created
   */

  public AsteroidBelt(int numberOfAsteroids, int minimumNumberOfVertex,
                      int mediumRadius) {
    this.mediumRadius = mediumRadius;
    this.asteroidsThreads = new AsteroidThread[numberOfAsteroids];
    this.initialNumberOfAsteroids = this.numberOfAsteroids = numberOfAsteroids;
    this.minimumNumberOfVertex = minimumNumberOfVertex;
    this.asteroids = new Asteroid[numberOfAsteroids];
    createTheAsteroids(asteroids);
    this.paused = false;
  }

  /**
   * Generates all the asteroids we need.
   * @param asteroids The array of asteroids where all the asteroids will be put.
   */
  public void createTheAsteroids(Asteroid[] asteroids) {
    for (int k = 0; k < numberOfAsteroids; k++) {
      int direction = (int) (Math.random() * 360);
      double minimumvelocity = Math.random() * 3;
      asteroids[k] = randomAsteroidGenerator(minimumvelocity,
                                             minimumNumberOfVertex,
                                             mediumRadius);
    }
  }

  /**
   * Almost randomically generates an asteroid. It only needs the minimum
   * velocity the asteroid could be at and its minimum number of vertex.
   * @param minimumVelocity Its minimum velocity! Ohhh!
   * @param minimumNumberOfVertex Its minimum number of vertex! Ohhh!
   * @param mediumRadius The medium radius of the asteroid will be created
   * @return An almost randomically generated asteroid! Wow!
   */

  public Asteroid randomAsteroidGenerator(double minimumVelocity,
                                          int minimumNumberOfVertex,
                                          int mediumRadius) {
    int direction = (int) (Math.random() * 360);
    double velocity = Math.random() * 3 + minimumVelocity;
    Point startingPoint = randomPointGenerator();
    return new Asteroid(startingPoint.x, startingPoint.y,
                        velocity * Math.cos(direction * Math.PI / 180),
                        velocity * Math.sin(direction * Math.PI / 180),
                        direction,
                        (int) (Math.random() * 4 + minimumNumberOfVertex),
                        mediumRadius);
  }

  /**
   * It randomically generates a point in space
   * @return A point in outer space
   */

  private Point randomPointGenerator() {
    int whichQuadrant = (int) (Math.random() * 8);
    return coordinateGenerator(whichQuadrant);
  }

  /**
   * Given a quadrant in outer space, it randomically returns a point of it
   * @param quadrant The given quadrant
   * @return A point placed in this quadrant
   */

  private Point coordinateGenerator(int quadrant) {
    int oneThirdOfUniversesWidth = (int) (500 / 3);
    int oneThirdOfUniversesHeight = (int) (500 / 3);
    Point point = new Point();
    int x_multiplicatorFactor = 0;
    int y_multiplicatorFactor = 0;
    switch (quadrant) {
      case 0:
        x_multiplicatorFactor = 0;
        break;
      case 1:
        x_multiplicatorFactor = 1;
        break;
      case 2:
        x_multiplicatorFactor = 2;
        break;
      case 3:
        x_multiplicatorFactor = 0;
        y_multiplicatorFactor = 1;
        break;
      case 4:
        x_multiplicatorFactor = 2;
        y_multiplicatorFactor = 1;
        break;
      case 5:
        x_multiplicatorFactor = 0;
        y_multiplicatorFactor = 2;
        break;
      case 6:
        x_multiplicatorFactor = 1;
        y_multiplicatorFactor = 2;
        break;
      case 7:
        x_multiplicatorFactor = 2;
        y_multiplicatorFactor = 2;
        break;
    }
    point.x = (int) (oneThirdOfUniversesWidth * Math.random() +
                     x_multiplicatorFactor * oneThirdOfUniversesWidth);
    point.y = (int) (oneThirdOfUniversesHeight * Math.random() +
                     y_multiplicatorFactor * oneThirdOfUniversesHeight);
    return point;
  }

  /**
   * Make things moooove!
   * @param panel A JPanel
   */
  public void start(JPanel panel) {
    this.panel = panel;
    for (int k = 0; k < numberOfAsteroids; k++) {
      asteroidsThreads[k] = new AsteroidThread(panel, asteroids[k]);
      asteroidsThreads[k].start();
    }
  }

  /**
   * Returns the number of "alive" asteroids
   * @return The number of "alive" asteroids
   */

  public synchronized int countAsteroids() {
    int numberOfAsteroids = 0, arrayLength = asteroids.length;
    for (int k = 0; k < arrayLength; k++) {
      if (!asteroids[k].wasHit)
        numberOfAsteroids++;
    }
    return numberOfAsteroids;
  }

  /**
   * Calculates the new number of asteroids
   * @return The new number of asteroids
   */
  private synchronized int calculateNewNumberOfAsteroids() {
    int numberOfAsteroids = 0;
    int numberOfNewAsteroids = 0;
    int oldArrayLength = asteroids.length;
    for (int k = 0; k < oldArrayLength; k++) {
      if (!asteroids[k].wasHit())
        numberOfAsteroids++;
      else if (asteroids[k].getVertex() > 4)
        numberOfNewAsteroids += 2;
    }
    return numberOfAsteroids + numberOfNewAsteroids;
  }

  /**
   * It renews all the asteroid belt. Checks if there's any asteroid that was hit
   * and destroy it. Depending the number of vertex it has, it can generate fragments or not.
   */
  public synchronized void renewAsteroids() {
    // calculate the number of new asteroids
    int newArrayLength = calculateNewNumberOfAsteroids();
    // creates the new arrays - asteroids and threads
    Asteroid[] newAsteroidsArray = new Asteroid[newArrayLength];
    AsteroidThread[] newAsteroidThreads = new AsteroidThread[newArrayLength];
    fillArrays(newAsteroidsArray, newAsteroidThreads);
    asteroids = newAsteroidsArray;
    asteroidsThreads = newAsteroidThreads;
  }

  /**
   * Refills the two arrays, creating the asteroids' fragments and their threads where needed.
   * @param newAsteroidsArray An array containing asteroids
   * @param newAsteroidThreads Another array, but this contains the asteroids' threads
   */

  private synchronized void fillArrays(Asteroid[] newAsteroidsArray,
                          AsteroidThread[] newAsteroidThreads) {
    int newArrayIndex = 0;
    int oldArrayLength = asteroids.length;
    // fills the arrays
    for (int k = 0; k < oldArrayLength; k++) {
      if (!asteroids[k].wasHit()) {
        // puts the asteroid and its thread in the array
        newAsteroidsArray[newArrayIndex] = asteroids[k];
        newAsteroidThreads[newArrayIndex] = asteroidsThreads[k];
        newArrayIndex++;
      }
      else if (asteroids[k].getVertex() > 4) {
        // creates a new asteroid and thread and puts them in their arrays
        for (int l = 0; l < 2; l++) {
          newAsteroidsArray[newArrayIndex] = createNewFragment(asteroids[k]);
          newAsteroidThreads[newArrayIndex] = new AsteroidThread(panel,
              newAsteroidsArray[newArrayIndex]);
          newAsteroidThreads[newArrayIndex].start();
          newArrayIndex++;
        }
      }
    }
  }

  /**
   * Creates a fragment of the asteroid
   * @param asteroid The "mother" asteroid
   * @return The fragment of the "mother" asteroid
   */
  private Asteroid createNewFragment(Asteroid asteroid) {
    double minimumVelocity = Math.random() * 3;
    int direction = (int) (Math.random() * 360);
    double velocity = Math.random() * 3 + minimumVelocity;
    return new Asteroid( (int) (asteroid.getX()), (int) (asteroid.getY()),
                        velocity * Math.cos(direction * Math.PI / 180),
                        velocity * Math.sin(direction * Math.PI / 180),
                        direction, asteroid.getVertex() - 1,
                        asteroid.getRadius() - 5);
  }

  /**
   * Returns an array which contains all the asteroids.
   * @return All the asteroids, inside an array
   */
  public Asteroid[] getAsteroids() {
    return asteroids;
  }

  /**
   * Returns an array which contains all the asteroid's threads.
   * @return All the asteroid's threads, inside an array
   */
  public AsteroidThread[] getAsteroidsThreads() {
    return asteroidsThreads;
  }

  /**
   * Returns the initial number of asteroids
   * @return The initial number of asteroids, couldn't you read?
   */

  public int getInitialNumberOfAsteroids() {
    return initialNumberOfAsteroids;
  }

  /**
   * It ends the game, I mean, destroy all asteroids.
   */
  public synchronized void endGame() {
    int arrayLength = asteroids.length;
    for (int k = 0; k < arrayLength; k++) {
      asteroids[k].hasBeenHit();
    }
  }

  /**
   * Pauses all asteroids' threads.
   */
  public synchronized void pauseThreads() {
    this.paused = true;
    int arrayLength = asteroidsThreads.length;
    for (int k = 0; k < arrayLength; k++) {
      asteroidsThreads[k].setPause(true);
    }
  }

  /**
   * Depauses all asteroids' threads.
   */
  public synchronized void depauseThreads() {
    this.paused = false;
    int arrayLength = asteroidsThreads.length;
    for (int k = 0; k < arrayLength; k++) {
      asteroidsThreads[k].setPause(false);
    }
  }

  /**
   * Returns if the threads are paused or not.
   * @return True if so, false case not
   */
  public boolean isPaused() {
    return paused;
  }
}